import UIKit

// 游戏回调
protocol GameCallback: NSObjectProtocol {
    func onGameOver(_ allOver: Bool)    // 游戏结束事件
    func onGotoLevel(_ level: Int)      // 进入关卡事件
    func onManMoved(_ moveCount: Int)   // 小人移动事件
}

// 游戏
class Game: NSObject {
    // 移动结点
    class MoveNode {
        private var mBox: BaseGameObject!   // 被推动的箱子
        private var mDirection: Int         // 移动方向

        /**
         * 构造方法。
         * @param box       被推动的箱子
         * @param direction 移动方向
         */
        public init(_ box: BaseGameObject!, direction: Int) {
            mBox = box
            mDirection = direction
        }
        
        /**
         * 获取箱子。
         */
        public var box: BaseGameObject! {
            get {
                return mBox
            }
        }
    
        /**
         * 获取方向。
         */
        public var direction: Int {
            get {
                return mDirection
            }
        }
    }

    // 状态
    public enum Status {
        case END        // 已结束
        case PLAYING    // 游戏中
    }

    private var mBoxCount: Int                              // 未到达目的地的箱子数
    private let mCallback: GameCallback                     // 回调
    private let mDrawer: CGContext                          // 绘图者
    private let mGameBoard: UIView                          // 游戏区域
    private var mLevel: Int                                 // 关卡数
    private var mMan: Man!                                  // 小人
    private var mMoveCount: Int                             // 移动次数
    private var mMoveList: Array<MoveNode>                  // 所有移动结点
    private var mStatus: Status                             // 状态
    public static var sBaseGameObjects: [[BaseGameObject]]! // 所有游戏物体

    /**
     * 构造方法。
     * @param callback  回调
     * @param drawer    绘图者
     * @param gameBoard 游戏区域
     */
    public init(_ callback: GameCallback, drawer: CGContext, gameBoard: UIView) {
        // 初始化成员
        mBoxCount = 0
        mCallback = callback
        mDrawer = drawer
        mGameBoard = gameBoard
        mLevel = 1
        mMoveCount = 0
        mMoveList = Array<MoveNode>()
        mStatus = .END

        super.init()

        // 进入第1关
        self.enterLevel()
    }

   /**
     * 进入关卡。
     */
    public func enterLevel() {
        // 小人停止跳舞
        if mMan != nil {
            mMan.stopDancing()
        }

        // 重绘游戏区域
        mDrawer.setFillColor(Const.GAME_BOARD_COLOR)
        let rect = CGRect(x: 0, y: 0, width: mGameBoard.frame.width, height: mGameBoard.frame.height)
        mDrawer.fill(rect)
        
        // 删除所有物体
        Game.sBaseGameObjects = [[BaseGameObject]](repeating: Array<BaseGameObject>(), count: Const.GAME_BOARD_GRID_COUNT_V)

        // 删除所有移动结点
        mMoveList.removeAll()

        // 读取关卡文件
        let fileName = "\(mLevel)"
        let path = Bundle.main.path(forResource: fileName, ofType: Const.LEVEL_FILE_EXTEND)!
        let text = try! String(contentsOfFile: path, encoding: String.Encoding.ascii)

        // 创建所有物体
        var x = 0, y = 0
        var type = BaseGameObject._Type.NOTHING
        mBoxCount = 0
        for index in text.indices {
            if text[index] == "\r\n" {
                x = 0
                y += 1
                continue
            }

            type = BaseGameObject._Type(rawValue: Int(String(text[index]))!)!

            if type == .MAN {
                // 创建小人
                mMan = Man(BaseGameObject(mDrawer, type: .BACKGROUND, x: x, y: y), drawer: mDrawer, gameBoard: mGameBoard, type: type, x: x, y: y)
                Game.sBaseGameObjects[y].append(mMan)
            } else if type == .BOX {
                // 创建箱子
                let box = Box(BaseGameObject(mDrawer, type: .BACKGROUND, x: x, y: y), drawer: mDrawer, type: type, x: x, y: y)
                Game.sBaseGameObjects[y].append(box)
                mBoxCount += 1
            } else {
                // 创建背景、目的地、墙
                Game.sBaseGameObjects[y].append(BaseGameObject(mDrawer, type: type, x: x, y: y))
            }

            x += 1
        }

        // 重置成员变量
        mMoveCount = 0
        mLevel = level
        mStatus = .PLAYING

        // 刷新游戏区域
        mGameBoard.setNeedsDisplay()
        
        // 发送通知
        mCallback.onGotoLevel(mLevel)
    }

    /**
     * 游戏结束。
     * @param allOver   全部通关则为 true，否则为 false
     */
    private func gameOver(_ allOver: Bool) {
        // 将状态设置为已结束
        mStatus = .END

        // 发送通知
        mCallback.onGameOver(allOver)
    }

    /**
     * 进入指定的关卡。
     * @param level 关卡数
     */
    public func gotoLevel(_ level: Int) {
          if level <= Const.LEVEL_COUNT {
            mLevel = level
            self.enterLevel()
        }
    }
    
    /**
     * 进入下一关卡。
     */
    public func gotoNextLevel() {
        if mLevel < Const.LEVEL_COUNT {
            mLevel += 1
            self.enterLevel()
        }
    }
    
    /**
     * 判断是否胜利。
     * @return 胜利则返回 true，否则返回 false
     */
    private func isWon() -> Bool {
        return mBoxCount == 0
    }

    /**
     * 获取当前关卡数。
     */
    public var level: Int {
        get {
            return mLevel
        }
    }

    /**
     * 按键事件的响应方法。
     * @param direction 方向
     */
    public func onFling(_ direction: Int) {
        // 如果没在玩，直接返回
        if mStatus != .PLAYING {
            return
        }

        // 移动小人。如果不能移动，则直接返回
        var moveResult = BaseGameObject.MoveResult.CANNOT_MOVE
        let movedBox = mMan.move(direction, moveResult: &moveResult)
        if moveResult == .NO_MOVE {
            return
        }

        // 重绘游戏区域
        mGameBoard.setNeedsDisplay() 

        // 增加移动次数
        mMoveCount += 1

        // 发送通知
        mCallback.onManMoved(mMoveCount)

        // 判断是否胜利
        if moveResult == .ARRIVED_ON_DEST {
            mBoxCount -= 1
            if self.isWon() {
                let allOver = mLevel == Const.LEVEL_COUNT
                self.gameOver(allOver)
                return
            }
        } else if moveResult == .LEFT_FROM_DEST {
            mBoxCount += 1
        }
        
        // 将本次移动加入列表
        mMoveList.append(MoveNode(movedBox, direction: direction))
    }

    /**
     * 重玩本关。
     */
    public func restart() {
        self.gotoLevel(mLevel)
    }

    /**
     * 回退，即撤消上一次操作。
     */
    public func undo() {
        if mMoveCount == 0 {
            return
        }
    
        // 计算方向（方向与最后一次移动的方向相反）
        var direction = 0
        let moveListNode = mMoveList.popLast()!
        switch moveListNode.direction {
            case Const.TAG_BUTTON_DOWN:
                direction = Const.TAG_BUTTON_UP
            case Const.TAG_BUTTON_LEFT:
                direction = Const.TAG_BUTTON_RIGHT
            case Const.TAG_BUTTON_RIGHT:
                direction = Const.TAG_BUTTON_LEFT
            case Const.TAG_BUTTON_UP:
                direction = Const.TAG_BUTTON_DOWN
            default:
                break
        }
        
        // 移动小人
        var moveResult = BaseGameObject.MoveResult.CANNOT_MOVE
        _ = mMan.move(direction, moveResult: &moveResult)
        
        // 移动箱子
        let box = moveListNode.box
        if box != nil {
            _ = box!.move(direction, moveResult: &moveResult)
            if moveResult == .LEFT_FROM_DEST {
                mBoxCount += 1
            } else if moveResult == .ARRIVED_ON_DEST {
                mBoxCount -= 1
            }
        }
        
        // 发送通知
        mMoveCount -= 1
        mCallback.onManMoved(mMoveCount)
    }
}
